﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Protocolo.Enums;
using Protocolo.Mensajes.Request;
using Protocolo.Mensajes.Response;
using Protocolo.Utilidades;
using Protocolo.AnchoDeBanda;

namespace Protocolo
{
    public class ComunicacionTCP
    {
        private const int ReceiveTimeout = 60000;

        private TcpClient tcpClient = null;
        private NetworkStream stream = null;

        public void ConectarCliente(string ip, int puerto)
        {
            try
            { 
                IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ip), puerto);
                tcpClient = new TcpClient();
                tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                tcpClient.ReceiveTimeout = ReceiveTimeout;
                tcpClient.Connect(endpoint);

                stream = tcpClient.GetStream();
            }
            catch (SocketException ex)
            {
                throw new Exception(string.Format("Error conectandose al servidor: {0}", ex.Message));
            }
        }

        public void EscucharRequests(TcpListener tcpListener)
        {
            try
            {

                tcpListener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                tcpListener.Start();

                tcpClient = tcpListener.AcceptTcpClient();

                stream = tcpClient.GetStream();
            }
            catch (SocketException ex)
            {
                throw new Exception(string.Format("Error escuchando en puerto TCP de clientes: {0}", ex.Message));
            }
        }

        public void EnviarMensaje(MensajeBase mensaje)
        {
            byte[] bytesMensaje = Encoding.ASCII.GetBytes(mensaje.ToString());
            stream.Write(bytesMensaje, 0, bytesMensaje.Length);
        }

        public MensajeBase RecibirMensaje()
        {
            string mensajeRecibido = string.Empty;
            byte[] bytes = new byte[1024]; //Buffer de 1 KB para mensajes del protocolo
            int bytesLeidos = 0;

            do
            {
                bytesLeidos = stream.Read(bytes, 0, bytes.Length);
                mensajeRecibido += Encoding.ASCII.GetString(bytes, 0, bytesLeidos);
            } while (stream.DataAvailable);

            return MensajeBase.ParsearMensaje(mensajeRecibido);
        }

        public void EnviarArchivo(byte[] archivo, AnchoDeBandaServidor anchoDeBandaServidor)
        {
            int bytesAEnviar = 0;
            int tamanioParte = (int)(anchoDeBandaServidor == null ? 1024 * 1024 : anchoDeBandaServidor.AnchoDeBandaXCliente * 1024);
            int cantPartes = (int)(Math.Ceiling((decimal)archivo.Length / (decimal)tamanioParte));
            
            for(int parte = 0; parte < cantPartes; parte++)
            {
                bytesAEnviar = Math.Min(tamanioParte, archivo.Length - (parte * tamanioParte));
                stream.Write(archivo, parte * tamanioParte, bytesAEnviar);

                //Actualizo el ancho de banda con lo enviado
                if (anchoDeBandaServidor != null)
                    anchoDeBandaServidor.ActualizarAnchoDeBandaCliente(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address.ToString(), bytesAEnviar / 1024);
            }
        }

        public string RecibirArchivo(string path, bool overwriteIfExists, long lenght, AnchoDeBandaServidor anchoDeBandaServidor)
        {
            string mensajeRecibido = string.Empty;
            byte[] bytes = new byte[(int)(anchoDeBandaServidor == null ? 1024 * 1024 : anchoDeBandaServidor.AnchoDeBandaXCliente * 1024)];
            byte[] parteArchivo;

            int bytesLeidos = 0;

            //Si me piden sobreescribir creo el archivo
            if(overwriteIfExists)
                Archivos.CrearArchivo(path);

            while (lenght > 0)
            {
                bytesLeidos = stream.Read(bytes, 0, bytes.Length);
                parteArchivo = bytes.Take(bytesLeidos).ToArray();
                Archivos.GuardarArchivo(path, parteArchivo);
                lenght -= bytesLeidos;

                //Actualizo el ancho de banda con lo recibido
                if (anchoDeBandaServidor != null)
                    anchoDeBandaServidor.ActualizarAnchoDeBandaCliente(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address.ToString(), bytesLeidos / 1024);
            }

            return Checksum.ObtenerChecksum(Archivos.LeerArchivo(path));
        }

        public void CerrarConexion()
        {
            if (stream != null)
                stream.Close();

            if (tcpClient != null)
                tcpClient.Close();
        }
    }
}
